/*
 * ccveprocessor.cpp
 *
 * Copyright 2012 The University of Auckland
 *
 *  Created on: 20/06/2012
 *      Author: cvan680
 */

#include "ccveprocessor.h"
#include "custommodules/ccvethesis/datastructures/mftgtexture.h"
#include "modules/flowreen/datastructures/streamlinetexture.h"
#include "tgt/textureunit.h"
#include "modules/flowreen/utils/flowmath.h"
#include <vector>

#include <windows.h>
#include <math.h>
//using std::vector;
using tgt::TextureUnit;

namespace voreen {

/*
 * TODO:
 * - Do we need a RenderProcessor? Or maybe VolumeProcessor? Or VolumeRaycaster..?
 * - Multiple Volume in-ports (ideally a property to choose the number of inports so to variably create or remove them)
 */

CcveProcessor::CcveProcessor()
    : RenderProcessor()
	, inport_(Port::INPORT, "volumecollection", 0)
    , volPort1_(Port::INPORT, "volumehandle.volumehandle1")
    , volPort2_(Port::INPORT, "volumehandle.volumehandle2")
    , outport_(Port::OUTPORT, "outport")
	, grammarfile_("grammarfile", "Texture Grammar", "Select texture grammar",
                VoreenApplication::app()->getResourcePath("textures"), "Texture Grammar files (*.teg)",
                FileDialogProperty::OPEN_FILE, Processor::INVALID_RESULT)
	, kernelSizeProp_("kernelSizeProp", "max. convolution kernel size field 1: ", 7, 1, 100)
	, kernelSizeProp2_("kernelSizeProp2", "max. convolution kernel size field 2: ", 7, 1, 100)
	, pixelSamplingProp_("pixelSamplingProp", "pixel sampling field 1: ", 50, 1, 10000)
	, pixelSamplingProp2_("pixelSamplingProp2", "pixel sampling field 2: ", 50, 1, 10000)
	, useAdaptiveKernelSizeProp_("useAdaptiveKernelSizeProp", "adaptive kernel size field 1: ", false)
	, useAdaptiveKernelSizeProp2_("useAdaptiveKernelSizeProp2", "adaptive kernel size field 2: ", false)
	, textureTypeProp1_(0)
	, textureTypeProp2_(0)
	, sparseP1_("sparseP1", "% distribution of sparsenoise texture 1: ", 7, 1, 100)
	, sparseP2_("sparseP2", "% distribution of sparsenoise texture 2: ", 7, 1, 100)
	, alignmentProp_(0)
	, thresholdProp_("magnitudeThreshold", "threshold for magnitude (%): ", tgt::vec2(0.0f, 100.0f), tgt::vec2(0.0f), tgt::vec2(100.0f))
	, sliceIndexProp_("sliceIndex", "slice number: ", 1, 1, 100)
	, sliceTexture_(0)
	, permutation_(0,1,2)
	, comboProp_(0)
	, previousComboType_(0)
	, patternWidthProp_("patternWidthProp", "pattern width: ", 40, 1, 1000)
	, patternXDisplacementProp_("patternXDisplacementProp", "pattern horizontal displacement: ", 0, -500, 500)
	, patternYDisplacementProp_("patternYDisplacementProp", "pattern vertical displacement: ", 0, -500, 500)
	, patternWeavingShadowProp_("patternWeavingDepthProp", "weaving pattern shadow percentage%: ", 20, 1, 100)
	, patternWeavingSolidShadowProp_("patternWeavingSolidShadowProp", "weaving pattern solid shadow: ", true)

	, overlayTopFieldProp_("overlayTopFieldProp", "Top field in overlay mode: ", 1, 1, 2)
	, overlayThresholdProp_("overlayThresholdProp", "Overlay threshold percentage: ", 50, 0, 100)

	, r_f1("f1_red", "Flow 1: red", true)
	, g_f1("f1_green", "Flow 1: green", false)
	, b_f1("f1_blue", "Flow 1: blue", false)
	, r_f2("f2_red", "Flow 2: red", false)
	, g_f2("f2_green", "Flow 2: green", false)
	, b_f2("f2_blue", "Flow 2: blue", true)
	, shader_(0)
	, LICchanged(true)
	, LICchanged2(true)
{

    /* REGISTER LIC PROPERTIES */
    alignmentProp_ = new OptionProperty<SliceAlignment>("sliceAlignment", "slice alignment: ");
    alignmentProp_->addOption("xy-plane", "xy-plane", PLANE_XY);
    alignmentProp_->addOption("xz-plane", "xz-plane", PLANE_XZ);
    alignmentProp_->addOption("zy-plane", "zy-plane", PLANE_ZY);
    alignmentProp_->onChange(
        CallMemberAction<CcveProcessor>(this, &CcveProcessor::onSliceAlignmentChange));

    thresholdProp_.setStepping(tgt::vec2(1.0f));
    thresholdProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::onThresholdChange));

    textureTypeProp1_ = new OptionProperty<InputTextureType>("textureType1", "LIC input texture for field 1: ");
    textureTypeProp1_->addOption("white_noise", "white noise", WHITE_NOISE);
    textureTypeProp1_->addOption("sparse_noise", "sparse noise", SPARSE_NOISE);
    textureTypeProp2_ = new OptionProperty<InputTextureType>("textureType2", "LIC input texture for field 2: ");
    textureTypeProp2_->addOption("white_noise", "white noise", WHITE_NOISE);
    textureTypeProp2_->addOption("sparse_noise", "sparse noise", SPARSE_NOISE);


	kernelSizeProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	kernelSizeProp2_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));
	pixelSamplingProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	pixelSamplingProp2_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));
	useAdaptiveKernelSizeProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	useAdaptiveKernelSizeProp2_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));
	alignmentProp_->onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	thresholdProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	sliceIndexProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	alignmentProp_->onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));
	thresholdProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));
	sliceIndexProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));

	textureTypeProp1_->onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	textureTypeProp2_->onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));
	sparseP1_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC));
	sparseP2_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::toggleLIC2));

    // register ports
    addPort(volPort1_);
    addPort(volPort2_);
    addPort(outport_);

    addProperty(grammarfile_);
    addProperty(kernelSizeProp_);
    addProperty(kernelSizeProp2_);
    addProperty(pixelSamplingProp_);
    addProperty(pixelSamplingProp2_);
    addProperty(useAdaptiveKernelSizeProp_);
    addProperty(useAdaptiveKernelSizeProp2_);
    addProperty(alignmentProp_);
    addProperty(thresholdProp_);
    addProperty(sliceIndexProp_);
    addProperty(textureTypeProp1_);
    addProperty(textureTypeProp2_);
    addProperty(sparseP1_);
    addProperty(sparseP2_);

    /* REGISTER BLEND PROPERTIES */
    r_f1.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    g_f1.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    b_f1.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    r_f2.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    g_f2.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    b_f2.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    patternWidthProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    patternXDisplacementProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    patternYDisplacementProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    patternWeavingShadowProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    patternWeavingSolidShadowProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    overlayTopFieldProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));
    overlayThresholdProp_.onChange(CallMemberAction<CcveProcessor>(this, &CcveProcessor::process));

    comboProp_ = new OptionProperty<ComboType>("comboType", "Combine fields using: ");
	comboProp_->addOption("weighted_addition", "weighted addition", WEIGHTED_ADDITION);
	comboProp_->addOption("overlay", "overlay", OVERLAY);
	comboProp_->addOption("masking_checkerboard", "masking with a checkerboard pattern", MASKING_CHECKERBOARD);
	comboProp_->addOption("masking_weaving", "masking with a weaving pattern", MASKING_WEAVING);
	comboProp_->addOption("masking_weaving2", "masking with a long weaving pattern", MASKING_WEAVING2);
	comboProp_->addOption("masking_diagonal", "masking with a diagonal pattern", MASKING_DIAGONAL);
	comboProp_->addOption("masking_horizontal", "masking with a horizontal pattern", MASKING_HORIZONTAL);
	comboProp_->addOption("masking_vertical", "masking with a vertical pattern", MASKING_VERTICAL);
	comboProp_->addOption("bumpmapping", "Bump map", BUMPMAPPING);
	comboProp_->addOption("doublebumpmapping", "Double Bump map", DOUBLEBUMPMAPPING);
	comboProp_->addOption("topological_blending", "topological blending", TOPOLOGICAL_BLENDING);
	comboProp_->addOption("modify_LIC", "use field 1 to modify LIC method of field 2", MODIFY_LIC);

    addProperty(r_f1);
    addProperty(g_f1);
    addProperty(b_f1);
    addProperty(r_f2);
    addProperty(g_f2);
    addProperty(b_f2);
    addProperty(comboProp_);
    addProperty(patternWidthProp_);
    addProperty(patternXDisplacementProp_);
    addProperty(patternYDisplacementProp_);
    addProperty(patternWeavingShadowProp_);
    addProperty(patternWeavingSolidShadowProp_);
    addProperty(overlayTopFieldProp_);
    addProperty(overlayThresholdProp_);
}

Processor* CcveProcessor::create() const {
    return new CcveProcessor();
}

void CcveProcessor::process() {
	tgt::vec2 viewportSize = outport_.getSize();

	int currentComboType = comboProp_->getValue();

	// this should be the same for both flowvolumes..
	//tgt::vec2 texDim2D = static_cast<tgt::vec2>(flow1.getFlowSliceDimensions(permutation_));

	// TODO if we have issues needing these values here, put getters for them on MFTGTexture!
	// ACTUALLY, either that, or we create a struct that contains both the data (or Texture object!!),
	// 			 AND all the values needed in the openGL function calls below.. that would be elegant!!

	int SAMPLING = pixelSamplingProp_.get();
	int KERNELSIZE = kernelSizeProp_.get();
	int SLICENO = 1; // FIXME get this from property - get property to auto update

	bool blaat = false;
	const VolumeFlow3D* volflow1 = checkVolumeHandleForFlowData<VolumeFlow3D>(volPort1_.getData(), blaat);
	const VolumeFlow3D* volflow2 = checkVolumeHandleForFlowData<VolumeFlow3D>(volPort2_.getData(), blaat);
	if(!volflow1){ return; }
	const Flow3D& flow1 = volflow1->getFlow3D();
	const Flow3D& flow2 = volflow2->getFlow3D();

    tgt::vec2 texDim2D = static_cast<tgt::vec2>(flow1.getFlowSliceDimensions(permutation_));
	float textureScaling = getTextureScalingFactor(viewportSize, texDim2D);

	tgt::vec2 outputSize(texDim2D * textureScaling);  // size of the quad on which the texture will be rendered
	tgt::vec2 slicePos = (viewportSize - outputSize) / 2.0f;    // offset for the quad on which the texture will be rendered

    Flow2D flow2D = flow1.extractSlice(permutation_, SLICENO);
    Flow2D flow2D2 = flow2.extractSlice(permutation_, SLICENO);

    const tgt::ivec2 dims(flow2D.dimensions_ * static_cast<int>(textureScaling));
	const tgt::vec2& THRESHOLDS(flow1.maxMagnitude_ * tgt::vec2(0.0f, 100.0f) / 100.0f);


    SimpleTexture<float> inputTexture1(flow2D.dimensions_, true);
    SimpleTexture<float> inputTexture2(flow2D2.dimensions_, true);
	inputTexture1.createWhiteNoise();
	inputTexture2.createWhiteNoise();

    int numElements = dims.x * dims.y;

    bool modlchanged = (previousComboType_ == MODIFY_LIC && currentComboType != MODIFY_LIC)
    				|| (previousComboType_ != MODIFY_LIC && currentComboType == MODIFY_LIC);

    if(LICchanged || !pixels1){
    	pixels1 = StreamlineTexture<float>::fastLIC( flow2D, inputTexture1, textureScaling, SAMPLING,
    												 KERNELSIZE, THRESHOLDS, useAdaptiveKernelSizeProp_.get() );
    }
	if(LICchanged2 || !pixels2 || modlchanged){
		if(comboProp_->getValue() == MODIFY_LIC){ inputTexture2.setData(pixels1); }
		pixels2 = StreamlineTexture<float>::fastLIC( flow2D2, inputTexture2, textureScaling, pixelSamplingProp2_.get(),
														 kernelSizeProp2_.get(), THRESHOLDS, useAdaptiveKernelSizeProp2_.get() );
	}

	if(LICchanged || LICchanged2 || !colours){
		colours = new float[numElements * 4];
	    memset(colours, 0, sizeof(float) * numElements * 4);
	}

    LICchanged = false;
    LICchanged2 = false;

    switch (comboProp_->getValue()) {
        case WEIGHTED_ADDITION:
            calculateWeightedAddition(numElements, pixels1, pixels2); // FIXME make weight adjustable
            break;
        case OVERLAY:
            calculateOverlay(numElements, pixels1, pixels2); // FIXME make threshold adjustable
            break;
        case MASKING_CHECKERBOARD:
            calculateCheckerboardPattern(pixels1, pixels2, dims.x, dims.y);
            break;
        case MASKING_WEAVING:
            calculateWeavingPattern(pixels1, pixels2, dims.x, dims.y);
            break;
        case MASKING_WEAVING2:
            calculateWeavingPattern2(pixels1, pixels2, dims.x, dims.y);
            break;
        case MASKING_DIAGONAL:
            calculateDiagonalPattern(pixels1, pixels2, dims.x, dims.y);
            break;
        case MASKING_HORIZONTAL:
            calculateHorizontalPattern(pixels1, pixels2, dims.x, dims.y);
            break;
        case MASKING_VERTICAL:
            calculateVerticalPattern(pixels1, pixels2, dims.x, dims.y);
            break;
        case BUMPMAPPING:
            calculateBumpMapping(pixels1, pixels2, dims.x, dims.y);
            break;
        case DOUBLEBUMPMAPPING:
            calculateDoubleBumpMapping(pixels1, pixels2, dims.x, dims.y);
            break;
        case TOPOLOGICAL_BLENDING:
            calculateTopologicalBlending(numElements, pixels1, pixels2);
            break;
        case MODIFY_LIC:
        	calculateModifyLIC(numElements, pixels2);
        	break;
        default:
            calculateWeightedAddition(numElements, pixels1, pixels2); // FIXME make weight adjustable
            break;
    }

	sliceTexture_ = new tgt::Texture(reinterpret_cast<GLubyte*>(colours),
		tgt::ivec3(dims, 1), GL_RGBA, GL_RGBA,
		GL_FLOAT, tgt::Texture::NEAREST);

    GLuint texID = 0;
    if (sliceTexture_ != 0) {
        sliceTexture_->uploadTexture();
        glBindTexture(GL_TEXTURE_2D, 0);
        //rebuildTexture_ = false;
        texID = sliceTexture_->getId();
    }

    outport_.activateTarget("CcveProcessor::process()");

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0f, viewportSize.x, 0.0f, viewportSize.y, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glActiveTexture(GL_TEXTURE0);
    if (texID != 0) {
        // Bind the texture containing the flow image, if one has been generated
        //
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, texID);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    }

    glPushAttrib(GL_TEXTURE_BIT);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTranslatef(slicePos.x, slicePos.y, 0.0f);
    glBegin(GL_QUADS);
        glMultiTexCoord2f(GL_TEXTURE0, 0.0f, 0.0f);
        glVertex2f(0.0f, 0.0f);

        glMultiTexCoord2f(GL_TEXTURE0, 1.0f, 0.0f);
        glVertex2f(outputSize.x, 0.0f);

        glMultiTexCoord2f(GL_TEXTURE0, 1.0f, 1.0f);
        glVertex2f(outputSize.x, outputSize.y);

        glMultiTexCoord2f(GL_TEXTURE0, 0.0f, 1.0f);
        glVertex2f(0.0f, outputSize.y);
    glEnd();

    glPopAttrib();
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glDisable(GL_TEXTURE_2D);

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    outport_.deactivateTarget();

    previousComboType_ = currentComboType;
}


/**
 * FIXME not sure if I need this? Maybe later. Adjusted from flowslicerenderer2 in that I
 * don't actually use the zoomProperty thingy, I just set it to ZOOM_FULL_SIZE in initialization.
 */
float CcveProcessor::getTextureScalingFactor(const tgt::vec2& viewportSize,
                                             const tgt::vec2& textureSize)
{
    float scaleWidth = viewportSize.x / textureSize.x;
    float scaleHeight = viewportSize.y / textureSize.y;
    float scaling = (scaleWidth <= scaleHeight) ? scaleWidth : scaleHeight;
    if (zoom_ != ZOOM_FULL_SIZE) {
        // find minimal scaling factor
        //
        float zoom = static_cast<float>(zoom_);
        if (zoom <= scaling)
            scaling = zoom;
        else {
            zoom_ = ZOOM_FULL_SIZE;
        }
    }
    return scaling;
}


float CcveProcessor::calculateVectorMagnitude( tgt::vec3 v ) {
	return sqrtf(tgt::dot(v, v));
}



void CcveProcessor::onThresholdChange() {
    tgt::vec2 threshold(thresholdProp_.get());
    if (threshold.x > threshold.y)
        threshold.y = threshold.x;
    thresholdProp_.set(threshold);
}

void CcveProcessor::onSliceAlignmentChange() {
    alignment_ = alignmentProp_->getValue();
    permutation_ = getCoordinatePermutation(alignment_);
    //rebuildTexture_ = true;
    //updateNumSlices();
    //rebuildShader();
}

tgt::ivec3 CcveProcessor::getCoordinatePermutation(const SliceAlignment& alignment) {
    switch (alignment) {
        case PLANE_XY:
            return tgt::ivec3(0, 1, 2);
        case PLANE_XZ:
            return tgt::ivec3(0, 2, 1);
        case PLANE_ZY:
            return tgt::ivec3(2, 1, 0);
    }
    return tgt::ivec3(0, 0, 0);
}

bool CcveProcessor::loadShader( const std::string& vertexShaderName,
                                const std::string& fragmentShaderName )
{
    if (shader_ == 0) {
        try {
            shader_ = ShdrMgr.loadSeparate(vertexShaderName.c_str(), fragmentShaderName.c_str(),
                generateShaderHeader(), false);
        } catch(tgt::Exception) {
            cout << "Failed to load shaders '" <<  vertexShaderName << "' & '"
                << fragmentShaderName << "'!";
            return false;
        }
        if (shader_ == 0)
            return false;

        shader_->deactivate();
        //shader_->setHeaders(generateShaderHeader());
        shader_->rebuild();
    }
    return true;
}

std::string CcveProcessor::generateShaderHeader(){
/* FIXME write all the values to shader header here
 *
    std::ostringstream oss;
    oss << "#define COLOR_MODE " << static_cast<int>(colorCodingMode_) << "\n";
    if (colorCodingModeProp_.getValue() == COLOR_CODING_MODE_MAGNITUDE)
        oss << "#define COLOR_TABLE " << colorTableProp_.getValue() << "\n";
    return oss.str();
    */
	return "";
}

tgt::vec4 CcveProcessor::getColour(float value) {
	const int colorTableSize = 4;
	tgt::vec3 colorTable[colorTableSize];
	colorTable[0] = tgt::vec3(0.0, 0.0, 0.0);    // black
	colorTable[1] = tgt::vec3(1.0, 0.0, 0.0);    // red
	colorTable[2] = tgt::vec3(1.0, 1.0, 0.0);    // yellow
	colorTable[3] = tgt::vec3(1.0, 1.0, 1.0);    // white

	float numColors = float(colorTableSize - 1);
	float v = clamp(value * numColors, 0.0, numColors);
	tgt::vec3 color = mix( colorTable[clamp2(int(v), 0, colorTableSize)],
						   colorTable[clamp2(int(ceil(v)), 0, colorTableSize)],
						   (v - int(v)) );
	return tgt::vec4(color, 1.0);
}

float CcveProcessor::clamp(float value, float min, float max) {
    return value < min ? min : (value > max ? max : value);
}

int CcveProcessor::clamp2(int value, int min, int max) {
    return value < min ? min : (value > max ? max : value);
}

tgt::vec3 CcveProcessor::mix(tgt::vec3 vec1, tgt::vec3 vec2, float fraction){
	return (fraction*vec2 + (1-fraction)*vec2);
}

void CcveProcessor::initialize() throw (VoreenException) {
    // call superclass function first
    RenderProcessor::initialize();

    zoom_ = ZOOM_FULL_SIZE;

    // loadSeparate with including a vertex shader?
    shader_ = ShdrMgr.loadSeparate("passthrough.vert", "colorlic.frag", RenderProcessor::generateHeader(), false);
}

void CcveProcessor::deinitialize() throw (VoreenException) {
    // free shader
    //ShdrMgr.dispose(shader_);
    //shader_ = 0;

    // call superclass function last
    RenderProcessor::deinitialize();
}

void CcveProcessor::toggleLIC(){
	LICchanged = true;
}
void CcveProcessor::toggleLIC2(){
	LICchanged2 = true;
}



void CcveProcessor::calculateWeightedAddition(int numElements, float* lic1, float* lic2){
	float weight = overlayThresholdProp_.get() / (float)100;
    for(int i=0; i < numElements; ++i){
    	colours[i*4+0] = 0.0;
    	colours[i*4+1] = 0.0;
    	colours[i*4+2] = 0.0;
    	colours[i*4+3] = 1.0;
    	if(r_f1.get()){
    		colours[i*4+0] += lic1[i]*weight;
    	}
    	if(r_f2.get()){
    		colours[i*4+0] += lic2[i]*(1-weight);
    	}

    	if(g_f1.get()){
    		colours[i*4+1] += lic1[i]*weight;
    	}
    	if(g_f2.get()){
    		colours[i*4+1] += lic2[i]*(1-weight);
    	}

    	if(b_f1.get()){
    		colours[i*4+2] += lic1[i]*weight;
    	}
    	if(b_f2.get()){
    		colours[i*4+2] += lic2[i]*(1-weight);
    	}
    }
}

void CcveProcessor::calculateOverlay(int numElements, float* lic1, float* lic2){
	bool field_one_on_top = overlayTopFieldProp_.get() == 1;
	float threshold = overlayThresholdProp_.get() / (float)100;
	for(int i=0; i<numElements; ++i){
		colours[i*4+0] = 0.0;
		colours[i*4+1] = 0.0;
		colours[i*4+2] = 0.0;
		colours[i*4+3] = 1.0;

		if((field_one_on_top && lic1[i] >= threshold) || (!field_one_on_top && lic2[i] <= threshold)){
			if(r_f1.get()){ colours[i*4+0] = lic1[i]; }
			if(g_f1.get()){ colours[i*4+1] = lic1[i]; }
			if(b_f1.get()){ colours[i*4+2] = lic1[i]; }
		}else{
			if(r_f2.get()){ colours[i*4+0] = lic2[i]; }
			if(g_f2.get()){ colours[i*4+1] = lic2[i]; }
			if(b_f2.get()){ colours[i*4+2] = lic2[i]; }
		}
	}
}

void CcveProcessor::calculateCheckerboardPattern(float* lic1, float* lic2, int xdim, int ydim){
    SimpleTexture<float> checkers(tgt::ivec2(xdim, ydim), true);
    checkers.createMaskCheckerboard(patternWidthProp_.get(), patternXDisplacementProp_.get(), patternYDisplacementProp_.get());
    const float* mask = checkers.getData();
    for(int i=0; i<checkers.getNumElements(); ++i){
		colours[i*4+0] = 0.0;
		colours[i*4+1] = 0.0;
		colours[i*4+2] = 0.0;
		colours[i*4+3] = 1.0;
    	if(mask[i]){
			if(r_f1.get()){ colours[i*4+0] = lic1[i]; }
			if(g_f1.get()){ colours[i*4+1] = lic1[i]; }
			if(b_f1.get()){ colours[i*4+2] = lic1[i]; }
		}else{
			if(r_f2.get()){ colours[i*4+0] = lic2[i]; }
			if(g_f2.get()){ colours[i*4+1] = lic2[i]; }
			if(b_f2.get()){ colours[i*4+2] = lic2[i]; }
		}
    }
}

void CcveProcessor::calculateWeavingPattern(float* lic1, float* lic2, int xdim, int ydim){

    //FIXME write one that behaves like this; except, at every "weave", it is random which field is shown.

	float shadeSize = ((float)patternWeavingShadowProp_.get() / (float)200);
	float shadeCutoff = 0.5 - shadeSize; // range [0.0, 0.5]
	bool solidShade = patternWeavingSolidShadowProp_.get();

    int i = 0;
    for(int x=0; x < xdim; ++x){
    	for(int y=0; y < ydim; ++y){
    		i = y*xdim + x;

			colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;

			bool xfold = ((x-patternXDisplacementProp_.get()) / patternWidthProp_.get())%2 == 1;
			bool yfold = ((y-patternYDisplacementProp_.get()) / patternWidthProp_.get())%2 == 1;
			bool show_field_1 = xfold == yfold; // if true, show field 1, otherwise show field 2

			// calculate shadow factor; frac stands for fraction of intensity that is shown (1.0 would be full)
			float frac = show_field_1 ?
					((float)(x-patternXDisplacementProp_.get()) / (float)patternWidthProp_.get())
				  : ((float)(y-patternYDisplacementProp_.get()) / (float)patternWidthProp_.get());

			frac = abs((frac - (int)frac) - 0.5);
			frac = frac > shadeCutoff ? (solidShade ? 0.0 : (0.5 - frac)/shadeSize) : 1.0;

			if(show_field_1){
				if(r_f1.get()){ colours[i*4+0] = lic1[i]*frac; }
				if(g_f1.get()){ colours[i*4+1] = lic1[i]*frac; }
				if(b_f1.get()){ colours[i*4+2] = lic1[i]*frac; }
			}else{
				if(r_f2.get()){ colours[i*4+0] = lic2[i]*frac; }
				if(g_f2.get()){ colours[i*4+1] = lic2[i]*frac; }
				if(b_f2.get()){ colours[i*4+2] = lic2[i]*frac; }
			}
    	}
    }
}

void CcveProcessor::calculateWeavingPattern2(float* lic1, float* lic2, int xdim, int ydim){

	float shadeSize = ((float)patternWeavingShadowProp_.get() / (float)200);
	float shadeCutoff = 0.5 - shadeSize; // range [0.0, 0.5]
	bool solidShade = patternWeavingSolidShadowProp_.get();

	int width = patternWidthProp_.get();
	int xdisp = patternXDisplacementProp_.get();
	int ydisp = patternYDisplacementProp_.get();

    int i = 0;
    for(int x=0; x < xdim; ++x){
    	for(int y=0; y < ydim; ++y){
    		i = y*xdim + x;

			colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;


			int xpos = x - xdisp;
			int ypos = y - ydisp;

			float xfr = (float)xpos / (float)width;
			float yfr = (float)ypos / (float)width;

			int xfrac = floor( xfr );
			int yfrac = floor( yfr );

			bool smallpatch =  xfrac % 4 == 0;
			bool evenrow = yfrac % 2 == 0;

			bool show_field_1 = evenrow == smallpatch; // if true, show field 1, otherwise show field 2

			// calculate shadow factor; frac stands for fraction of intensity that is shown (1.0 would be full)
			float frac = 0.5;

			if(show_field_1){
				if(smallpatch){
					frac = ((float)(x-xdisp) / (float)width);
				}else{
					frac = (float)(((x-xdisp) % (4*width)) - width) / (float)(3.0*width);
				}
			}else{
				frac = ((float)(y-ydisp) / (float)width);
			}

			frac = abs((frac - (int)frac) - 0.5);
			frac = frac > shadeCutoff ? (solidShade ? 0.0 : (0.5 - frac)/shadeSize) : 1.0;

			if(show_field_1){
				if(r_f1.get()){ colours[i*4+0] = lic1[i]*frac; }
				if(g_f1.get()){ colours[i*4+1] = lic1[i]*frac; }
				if(b_f1.get()){ colours[i*4+2] = lic1[i]*frac; }
			}else{
				if(r_f2.get()){ colours[i*4+0] = lic2[i]*frac; }
				if(g_f2.get()){ colours[i*4+1] = lic2[i]*frac; }
				if(b_f2.get()){ colours[i*4+2] = lic2[i]*frac; }
			}
    	}
    }
}

void CcveProcessor::calculateDiagonalPattern(float* lic1, float* lic2, int xdim, int ydim){
	int i = 0;
	for(int x=0; x < xdim; ++x){
		for(int y=0; y < ydim; ++y){
			i = y*xdim + x;

			colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;

			bool show_field_1 = (((x+patternXDisplacementProp_.get())+(y+patternYDisplacementProp_.get())) / patternWidthProp_.get())%2 == 1;

			if(show_field_1){
				if(r_f1.get()){ colours[i*4+0] = lic1[i]; }
				if(g_f1.get()){ colours[i*4+1] = lic1[i]; }
				if(b_f1.get()){ colours[i*4+2] = lic1[i]; }
			}else{
				if(r_f2.get()){ colours[i*4+0] = lic2[i]; }
				if(g_f2.get()){ colours[i*4+1] = lic2[i]; }
				if(b_f2.get()){ colours[i*4+2] = lic2[i]; }
			}
		}
	}
}

void CcveProcessor::calculateVerticalPattern(float* lic1, float* lic2, int xdim, int ydim){
    int i = 0;
    for(int x=0; x < xdim; ++x){
    	for(int y=0; y < ydim; ++y){
    		i = y*xdim + x;

    		colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;

			bool show_field_1 = ((x+patternXDisplacementProp_.get()) / patternWidthProp_.get())%2 == 1; // if true, show field 1, otherwise show field 2

			if(show_field_1){
				if(r_f1.get()){ colours[i*4+0] = lic1[i]; }
				if(g_f1.get()){ colours[i*4+1] = lic1[i]; }
				if(b_f1.get()){ colours[i*4+2] = lic1[i]; }
			}else{
				if(r_f2.get()){ colours[i*4+0] = lic2[i]; }
				if(g_f2.get()){ colours[i*4+1] = lic2[i]; }
				if(b_f2.get()){ colours[i*4+2] = lic2[i]; }
			}
    	}
    }
}

void CcveProcessor::calculateHorizontalPattern(float* lic1, float* lic2, int xdim, int ydim){
    int i = 0;
    for(int x=0; x < xdim; ++x){
    	for(int y=0; y < ydim; ++y){
    		i = y*xdim + x;
			colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;

			bool show_field_1 = ((y+patternYDisplacementProp_.get()) / patternWidthProp_.get())%2 == 1; // if true, show field 1, otherwise show field 2

			if(show_field_1){
				if(r_f1.get()){ colours[i*4+0] = lic1[i]; }
				if(g_f1.get()){ colours[i*4+1] = lic1[i]; }
				if(b_f1.get()){ colours[i*4+2] = lic1[i]; }
			}else{
				if(r_f2.get()){ colours[i*4+0] = lic2[i]; }
				if(g_f2.get()){ colours[i*4+1] = lic2[i]; }
				if(b_f2.get()){ colours[i*4+2] = lic2[i]; }
			}
    	}
    }
}

void CcveProcessor::calculateBumpMapping(float* lic1, float* lic2, int xdim, int ydim){
	int i = 0;
	int k = patternWidthProp_.get()-1;
	bool gradient_x = patternWeavingSolidShadowProp_.get();

	for(int x=0; x < xdim; ++x){
		for(int y=0; y < ydim; ++y){
			i = y*xdim + x;
			colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;

			float grad = 0.0;
			//DO DIAGONAL GRADIENT
			if(b_f2.get()){
				if(gradient_x){
					if(!(x==0 || y==0)){
						grad = calcGaussian(lic2, xdim, ydim, k, x-1, y-1)
							 - calcGaussian(lic2, xdim, ydim, k, x, y);
					}
				}else{
					if(!(x+1 >= xdim || y==0)){
						grad = calcGaussian(lic2, xdim, ydim, k, x+1, y-1)
							 - calcGaussian(lic2, xdim, ydim, k, x, y);
					}
				}
			}else{
				grad = gradient_x
					  ? ( x+1 < xdim
						  ? calcGaussian(lic2, xdim, ydim, k, x+1, y)
							  - calcGaussian(lic2, xdim, ydim, k, x, y)
						  : 0.0 )
					  : ( y+1 < ydim
						  ? calcGaussian(lic2, xdim, ydim, k, x, y+1)
							  - calcGaussian(lic2, xdim, ydim, k, x, y)
						  : 0.0 );
			}

			// normalize from [-1,1] to [0,2]
			grad = grad+1.0;

			if(r_f1.get()){ colours[i*4+0] = lic1[i] * grad; }
			if(g_f1.get()){ colours[i*4+1] = lic1[i] * grad; }
			if(b_f1.get()){ colours[i*4+2] = lic1[i] * grad; }
		}
	}
}



void CcveProcessor::calculateDoubleBumpMapping(float* lic1, float* lic2, int xdim, int ydim){
	int i = 0;
	int k = patternWidthProp_.get()-1;
	bool boolean = patternWeavingSolidShadowProp_.get();

	for(int x=0; x < xdim; ++x){
		for(int y=0; y < ydim; ++y){
			i = y*xdim + x;
			colours[i*4+0] = 0.0;
			colours[i*4+1] = 0.0;
			colours[i*4+2] = 0.0;
			colours[i*4+3] = 1.0;

			float gra1 = 0.0;
			float gra2 = 0.0;
			//DO DIAGONAL GRADIENT
			gra1 = ( x+1 < xdim ? calcGaussian(lic1, xdim, ydim, k, x+1, y)
									- calcGaussian(lic1, xdim, ydim, k, x, y)
								: 0.0 );
			gra2 = ( y+1 < ydim ? calcGaussian(lic2, xdim, ydim, k, x, y+1)
									- calcGaussian(lic2, xdim, ydim, k, x, y)
								: 0.0 );

			if(boolean){
				// normalize from [-1,1] to [0,1]
				gra1 = (gra1+1.0)/2.0;
				gra2 = (gra2+1.0)/2.0;

				if(r_f1.get()){ colours[i*4+0] = gra1 * gra2; }
				if(g_f1.get()){ colours[i*4+1] = gra1 * gra2; }
				if(b_f1.get()){ colours[i*4+2] = gra1 * gra2; }
			} else {
				// normalize from [-1,1] to [0,2]
				gra1 = (gra1+1.0)/2.0;
				gra2 = (gra2+1.0)/2.0;
				if(r_f1.get()){ colours[i*4+0] += .5*gra1; }
				if(g_f1.get()){ colours[i*4+1] += .5*gra1; }
				if(b_f1.get()){ colours[i*4+2] += .5*gra1; }
				if(r_f2.get()){ colours[i*4+0] += .5*gra2; }
				if(g_f2.get()){ colours[i*4+1] += .5*gra2; }
				if(b_f2.get()){ colours[i*4+2] += .5*gra2; }
			}
		}
	}
}


void CcveProcessor::calculateTopologicalBlending(int numElements, float* lic1, float* lic2){
    // FIXME write this method
}


void CcveProcessor::calculateModifyLIC(int numElements, float* lic){
	for(int i=0; i < numElements; ++i){
		colours[i*4+0] = lic[i];
		colours[i*4+1] = lic[i];
		colours[i*4+2] = lic[i];
		colours[i*4+3] = 1.0;
	}
}

/**
 * r,g,b	in [0, 1]
 * h		in [0, 360]
 * s,v		in [0, 1]
 */
tgt::vec3 CcveProcessor::RGBtoHSV(tgt::vec3 rgb){
	if(rgb.r > 1.0 || rgb.r < 0.0 || rgb.g > 1.0 || rgb.g < 0.0 || rgb.b > 1.0 || rgb.b < 0.0){
		return tgt::vec3(0.0, 0.0, 0.0);
	}
	float minRGB = min(rgb.r, min(rgb.g, rgb.b));
	float maxRGB = max(rgb.r, max(rgb.g, rgb.b));
	// shade of gray
	if(minRGB == maxRGB){
		return tgt::vec3(0.0, 0.0, minRGB);
	}
	float d = rgb.r==minRGB ? rgb.g - rgb.b : (rgb.b==minRGB ? rgb.r-rgb.g : rgb.b-rgb.r);
	float h = rgb.r==minRGB ? 3.0 : (rgb.b==minRGB ? 1.0 : 5.0);
	// HSV
	return tgt::vec3( ( 60*(h - d/(maxRGB - minRGB)) ),
					  (maxRGB - minRGB) / maxRGB,
					  maxRGB );
}

/**
 * r,g,b	in [0, 1]
 * h		in [0, 360]
 * s,v		in [0, 1]
 */
tgt::vec3 CcveProcessor::HSVtoRGB(tgt::vec3 hsv){
	if(hsv.x > 360.0 || hsv.x < 0.0 || hsv.y > 1.0 || hsv.y < 0.0 || hsv.z > 1.0 || hsv.z < 0.0){
		return tgt::vec3(0.0, 0.0, 0.0);
	}

	float chroma;
	float hdash;
	float x;
	tgt::vec3 rgb(0.0, 0.0, 0.0);

	chroma = hsv.y * hsv.z; // chroma = V * S
	hdash = hsv.x / 60.0; // H' = H / 60
	int hdash_int = floor(hdash); // because c++ doesnt like modulo operation on floats
	float hdash_rest = hdash - hdash_int;
	x = chroma * (1.0 - abs((hdash_int % 2) + hdash_rest - 1.0));

	if(hdash < 1.0) {
		rgb.r = chroma;
		rgb.g = x;
	} else if(hdash < 2.0) {
		rgb.r = x;
		rgb.g = chroma;
	} else if(hdash < 3.0) {
		rgb.g = chroma;
		rgb.b = x;
	} else if(hdash < 4.0) {
		rgb.g= x;
		rgb.b = chroma;
	} else if(hdash < 5.0) {
		rgb.r = x;
		rgb.b = chroma;
	} else if(hdash < 6.0) {
		rgb.r = chroma;
		rgb.b = x;
	}

	float min = hsv.z - chroma;
	rgb.r += min;
	rgb.g += min;
	rgb.b += min;

	return rgb;
}

float CcveProcessor::calcGaussian(float* texture, int xdim, int ydim, int kernelsize, int x, int y){

	int xmin = x - kernelsize >= 0 ? x - kernelsize : 0;
	int xmax = x + kernelsize < xdim ? x + kernelsize : xdim-1;
	int ymin = y - kernelsize >= 0 ? y - kernelsize : 0;
	int ymax = y + kernelsize < ydim ? y + kernelsize : ydim-1;

	float value = 0.0;
	for(int xs=xmin; xs <= xmax; xs++){
		for(int ys=ymin; ys <= ymax; ys++){
			value += texture[ys*xdim + xs];
		}
	}
	value /= ((xmax+1-xmin)*(ymax+1-ymin)); // average
	return value;
}


} // namespace
